404 template not found
The template is set for this widget, but that template was not found in the skin.
template: mini_player_global.tpl
directory: jrPlaylist
},
stop: function() {
console.log('MiniPlayer: Stop via jPlayer');
$('#mini_player_jp').jPlayer("stop");
// Clear playback state when stopped
try {
localStorage.removeItem('miniPlayerState');
} catch(e) {
console.warn('Could not clear playback state:', e);
}
}
};
// If there was a pending track queued before jPlayer was ready, play it now
if (window.MiniPlayer.pendingTrack) {
console.log('MiniPlayer: Found pending track, playing now');
console.log('Pending track data:', window.MiniPlayer.pendingTrack);
var pending = window.MiniPlayer.pendingTrack;
window.MiniPlayer.pendingTrack = null;
window.MiniPlayer.loadTrack(pending.track, pending.autoPlay);
} else {
console.log('MiniPlayer: No pending track to play');
}
$(document).ready(function(){
console.log('MINI PLAYER: Initializing jPlayer...');
// Progress Ring Animation Variables
var progressRing = document.getElementById('progress-ring-fill');
var progressRingCircumference = 2 * Math.PI * 29; // r=29
// Color Adaptive Theming
var currentThemeColor = '#1db954';
function extractColorFromImage(imgElement) {
try {
// Create a canvas to extract colors
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = 1;
canvas.height = 1;
// Draw the image
ctx.drawImage(imgElement, 0, 0, 1, 1);
// Get the pixel data
var pixelData = ctx.getImageData(0, 0, 1, 1).data;
// Convert to hex
var r = pixelData[0];
var g = pixelData[1];
var b = pixelData[2];
// Enhance saturation for better visual effect
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
var saturation = max === 0 ? 0 : (max - min) / max;
if (saturation < 0.3) {
// Boost saturation if image is too gray
var boost = 1.5;
var avg = (r + g + b) / 3;
r = Math.min(255, Math.floor(avg + (r - avg) * boost));
g = Math.min(255, Math.floor(avg + (g - avg) * boost));
b = Math.min(255, Math.floor(avg + (b - avg) * boost));
}
return 'rgb(' + r + ',' + g + ',' + b + ')';
} catch(e) {
console.warn('Color extraction failed:', e);
return '#1db954'; // Default green
}
}
function applyThemeColor(color) {
if (!color || color === currentThemeColor) return;
currentThemeColor = color;
console.log('Applying theme color:', color);
// Update progress ring gradient
var gradient = document.querySelector('#progressGradient');
if (gradient) {
var stops = gradient.querySelectorAll('stop');
if (stops.length >= 2) {
stops[0].setAttribute('style', 'stop-color:' + color + ';stop-opacity:1');
// Create lighter version for second stop
var rgb = color.match(/\d+/g);
if (rgb) {
var lighterColor = 'rgb(' +
Math.min(255, parseInt(rgb[0]) + 30) + ',' +
Math.min(255, parseInt(rgb[1]) + 30) + ',' +
Math.min(255, parseInt(rgb[2]) + 30) + ')';
stops[1].setAttribute('style', 'stop-color:' + lighterColor + ';stop-opacity:1');
}
}
}
// Keep mini player background consistently dark - no color changes
var miniPlayer = document.getElementById('mini-player');
if (miniPlayer) {
// Always use the same dark background regardless of artwork
miniPlayer.style.background = 'rgba(18,18,18,0.98)';
}
// Keep waveform background consistently dark - no color changes
var waveformContainer = document.querySelector('.mini-player-waveform');
if (waveformContainer) {
// Always use the same dark background with light blue accent
waveformContainer.style.background = 'linear-gradient(90deg, rgba(135,206,250,0.06) 0%, rgba(135,206,250,0.02) 100%)';
waveformContainer.style.backgroundColor = 'rgba(12,12,12,0.6)';
waveformContainer.style.borderColor = 'rgba(135,206,250,0.15)';
}
}
function updateProgressRing(progress) {
if (!progressRing) return;
// Progress is 0 to 1
var offset = progressRingCircumference - (progress * progressRingCircumference);
progressRing.style.strokeDashoffset = offset;
}
// Initialize Audio Waveform Visualizer (Live)
var waveformCanvas = document.getElementById('waveform-canvas');
var waveformCtx = waveformCanvas ? waveformCanvas.getContext('2d') : null;
var audioContext = null;
var analyser = null;
var dataArray = null;
var bufferLength = 0;
var animationId = null;
var hoverX = -1;
// Initialize Progress Waveform (Static)
var progressWaveformCanvas = document.getElementById('progress-waveform-canvas');
var progressWaveformCtx = progressWaveformCanvas ? progressWaveformCanvas.getContext('2d') : null;
var progressWaveformData = [];
var progressHoverX = -1;
function initWaveform(audioElement) {
if (!waveformCanvas || !waveformCtx) return;
if (audioContext) return; // Already initialized
try {
// Create audio context and analyser
audioContext = new (window.AudioContext || window.webkitAudioContext)();
analyser = audioContext.createAnalyser();
analyser.fftSize = 128; // Smaller for performance
analyser.smoothingTimeConstant = 0.8;
// IMPORTANT: Only create source once
var source = audioContext.createMediaElementSource(audioElement);
source.connect(analyser);
analyser.connect(audioContext.destination);
bufferLength = analyser.frequencyBinCount;
dataArray = new Uint8Array(bufferLength);
// Set canvas size
waveformCanvas.width = waveformCanvas.offsetWidth;
waveformCanvas.height = waveformCanvas.offsetHeight;
console.log('Waveform: Initialized successfully');
drawWaveform();
} catch(e) {
console.error('Waveform: Initialization failed', e);
// Continue without waveform if it fails
audioContext = null;
analyser = null;
}
}
function drawWaveform() {
if (!analyser || !waveformCtx || !dataArray) return;
animationId = requestAnimationFrame(drawWaveform);
analyser.getByteFrequencyData(dataArray);
var width = waveformCanvas.width;
var height = waveformCanvas.height;
// Clear canvas
waveformCtx.clearRect(0, 0, width, height);
// Draw bars
var barWidth = (width / bufferLength) * 2.0;
var barHeight;
var x = 0;
for(var i = 0; i < bufferLength; i++) {
barHeight = (dataArray[i] / 255) * height * 0.8;
// Create gradient with consistent light blue color
var gradient = waveformCtx.createLinearGradient(0, height - barHeight, 0, height);
gradient.addColorStop(0, '#87CEEB'); // Light blue (skyblue)
gradient.addColorStop(1, '#ADD8E6'); // Lighter blue
waveformCtx.fillStyle = gradient;
waveformCtx.fillRect(x, height - barHeight, barWidth, barHeight);
x += barWidth + 1;
}
}
function stopWaveform() {
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
}
if (waveformCtx && waveformCanvas) {
waveformCtx.clearRect(0, 0, waveformCanvas.width, waveformCanvas.height);
}
}
// Enhanced waveform patterns - Based on real mastered songs
var waveformPatterns = {
// Pattern 1: Modern Pop/Rock - Dynamic with variation
modernPop: function(samples) {
var data = [];
for (var i = 0; i < samples; i++) {
var progress = i / samples;
// Intro/verse/chorus/bridge structure with MORE dynamics
var section = Math.floor(progress * 8);
var sectionType = section % 4;
var baseLevel;
if (sectionType === 0) baseLevel = 0.55; // Intro - quieter
else if (sectionType === 1) baseLevel = 0.75; // Verse - moderate
else if (sectionType === 2) baseLevel = 0.88; // Chorus - loud
else baseLevel = 0.68; // Bridge - medium
// More pronounced beats
var beat = (i % 8 < 1) ? 0.15 : (i % 8 === 4) ? 0.1 : 0;
// Enhanced melodic variation
var melody = Math.abs(Math.sin(i / 7) * Math.cos(i / 11)) * 0.2;
// Micro dynamics for realism
var microDynamics = (Math.random() - 0.5) * 0.08;
var level = baseLevel + beat + melody + microDynamics;
data.push(Math.min(0.95, Math.max(0.45, level)));
}
return data;
},
// Pattern 2: Electronic/EDM - Enhanced dynamics with dramatic builds
edm: function(samples) {
var data = [];
for (var i = 0; i < samples; i++) {
var progress = i / samples;
// More dramatic build-ups and drops
var buildSection = (progress < 0.22 || (progress > 0.5 && progress < 0.72));
var dropSection = (progress > 0.22 && progress < 0.28) || (progress > 0.72 && progress < 0.78);
if (buildSection) {
// Gradual build with tension
var localProgress = (progress < 0.22) ? (progress / 0.22) : ((progress - 0.5) / 0.22);
var tension = Math.pow(localProgress, 1.5); // Exponential build
var hihat = Math.abs(Math.sin(i / 2)) * 0.12 * localProgress;
data.push(0.35 + (tension * 0.52) + hihat);
} else if (dropSection) {
// The drop - more variation
var dropProgress = (progress < 0.28) ? ((progress - 0.22) / 0.06) : ((progress - 0.72) / 0.06);
data.push(0.18 + Math.random() * 0.15 + (Math.sin(dropProgress * Math.PI * 4) * 0.1));
} else {
// Full energy sections with more variation
var energy = 0.78 + Math.abs(Math.sin(i / 3.5) * Math.cos(i / 5)) * 0.18;
var kick = (i % 4 === 0) ? 0.12 : 0;
var snare = (i % 8 === 4) ? 0.08 : 0;
data.push(Math.min(0.96, energy + Math.max(kick, snare)));
}
}
return data;
},
// Pattern 3: Classical/Orchestral - MAXIMUM dynamics, dramatic crescendos
orchestral: function(samples) {
var data = [];
for (var i = 0; i < samples; i++) {
var progress = i / samples;
// Dramatic multi-movement structure
var movement = Math.floor(progress * 3);
// Overall phrase arc with more drama
var phrase = Math.sin(progress * Math.PI * 2.5) * 0.45 + 0.45;
// Multiple crescendos and diminuendos
var crescendo = Math.pow(Math.sin(progress * Math.PI * 5), 3) * 0.35;
// String section swells
var strings = Math.abs(Math.sin(i / 12) * Math.cos(i / 9)) * 0.25;
// Quiet passages - more dramatic
var quietSection = (progress > 0.25 && progress < 0.35) || (progress > 0.65 && progress < 0.72);
var quietness = quietSection ? -0.45 : 0;
// Climactic moments
var climax = (progress > 0.45 && progress < 0.52) || (progress > 0.85 && progress < 0.92);
var climaxBoost = climax ? 0.25 : 0;
// Natural orchestral breathing
var natural = (Math.random() - 0.5) * 0.12;
var level = phrase + crescendo + strings + quietness + climaxBoost + natural;
data.push(Math.min(0.98, Math.max(0.08, level)));
}
return data;
},
// Pattern 4: Hip-Hop/R&B - More dynamic with bass drops and vocal peaks
hiphop: function(samples) {
var data = [];
for (var i = 0; i < samples; i++) {
var progress = i / samples;
// Intro/verse/hook/outro structure
var section = Math.floor(progress * 8);
var isIntro = section === 0;
var isVerse = section === 1 || section === 3 || section === 5;
var isHook = section === 2 || section === 4 || section === 6;
var isOutro = section === 7;
var baseLevel;
if (isIntro) baseLevel = 0.45;
else if (isVerse) baseLevel = 0.62;
else if (isHook) baseLevel = 0.82;
else baseLevel = 0.50; // outro
// 808 bass slides and hits
var bassHit = (i % 16 === 0) ? 0.18 : (i % 16 === 7) ? 0.14 : 0;
var bassSlide = Math.abs(Math.sin(i / 9)) * 0.1;
// Drum pattern - more pronounced
var kick = (i % 8 === 0) ? 0.15 : 0;
var snare = (i % 8 === 4) ? 0.13 : 0;
// Hi-hat rolls
var hihat = (i % 2 === 0) ? Math.abs(Math.sin(i / 2.5)) * 0.09 : 0;
// Vocal ad-libs and harmonies
var vocal = Math.abs(Math.sin(i / 13) * Math.cos(i / 17)) * 0.15;
var level = baseLevel + Math.max(bassHit, kick, snare) + bassSlide + hihat + vocal;
data.push(Math.min(0.94, Math.max(0.38, level + (Math.random() - 0.5) * 0.06)));
}
return data;
},
// Pattern 5: Acoustic/Singer-Songwriter - Enhanced natural dynamics
acoustic: function(samples) {
var data = [];
for (var i = 0; i < samples; i++) {
var progress = i / samples;
// Verse-chorus-bridge structure with MORE contrast
var section = Math.floor(progress * 6);
var isVerse = section === 0 || section === 2 || section === 4;
var isChorus = section === 1 || section === 3;
var isBridge = section === 5;
var baseStructure;
if (isVerse) baseStructure = 0.38 + Math.sin(progress * Math.PI * 4) * 0.18;
else if (isChorus) baseStructure = 0.65 + Math.sin(progress * Math.PI * 3) * 0.22;
else baseStructure = 0.48; // bridge
// Guitar strumming with more attack
var strum = (i % 6 === 0) ? 0.16 : (i % 6 === 3) ? 0.09 : Math.abs(Math.sin(i / 3.5)) * 0.06;
// Vocal phrasing - more dramatic peaks
var vocal = Math.abs(Math.sin(i / 11) * Math.cos(i / 14)) * 0.28;
// Fingerpicking detail
var picking = (i % 4 === 0) ? 0.08 : 0;
// Natural breathing and dynamics
var breath = Math.sin(i / 18) * 0.14;
// Organic variations
var variation = (Math.random() - 0.5) * 0.12;
var level = baseStructure + strum + vocal + picking + breath + variation;
data.push(Math.min(0.92, Math.max(0.12, level)));
}
return data;
},
// Pattern 6: Rock/Metal - DRAMATIC dynamic contrast
rock: function(samples) {
var data = [];
for (var i = 0; i < samples; i++) {
var progress = i / samples;
// Intro/verse/pre-chorus/chorus/breakdown/solo/chorus/outro
var section = Math.floor(progress * 8);
var baseEnergy;
if (section === 0) baseEnergy = 0.42; // Intro - building
else if (section === 1 || section === 5) baseEnergy = 0.58; // Verse - moderate
else if (section === 2) baseEnergy = 0.72; // Pre-chorus - building
else if (section === 3 || section === 6) baseEnergy = 0.89; // Chorus - explosive
else if (section === 4) baseEnergy = 0.35; // Breakdown - quiet
else baseEnergy = 0.78; // Outro - heavy
// Double bass drum patterns
var kick = (i % 2 === 0) ? 0.14 : 0.08;
// Snare with ghost notes
var snare = (i % 4 === 2) ? 0.16 : (i % 8 === 6) ? 0.07 : 0;
// Power chord chugs
var guitar = Math.abs(Math.sin(i / 4.5) * Math.cos(i / 6)) * 0.18;
// Guitar harmonics in quiet parts
var harmonics = (section === 4) ? Math.abs(Math.sin(i / 8)) * 0.15 : 0;
// Cymbal crashes and rides
var cymbals = (i % 16 === 0 && section >= 3) ? 0.14 : Math.abs(Math.sin(i / 3)) * 0.06;
// Bass guitar presence
var bass = Math.abs(Math.sin(i / 7)) * 0.12;
var level = baseEnergy + Math.max(kick, snare) + guitar + harmonics + cymbals + bass;
data.push(Math.min(0.97, Math.max(0.28, level + (Math.random() - 0.5) * 0.07)));
}
return data;
}
};
var waveformCaptureInterval = null;
var currentWaveformPattern = null;
function generateProgressWaveform() {
if (!progressWaveformCanvas) return;
// Clear previous capture
if (waveformCaptureInterval) {
clearInterval(waveformCaptureInterval);
}
// Set canvas size
progressWaveformCanvas.width = progressWaveformCanvas.offsetWidth;
progressWaveformCanvas.height = progressWaveformCanvas.offsetHeight;
// Select a realistic waveform pattern
var patternNames = ['modernPop', 'edm', 'orchestral', 'hiphop', 'acoustic', 'rock'];
var randomPattern = patternNames[Math.floor(Math.random() * patternNames.length)];
currentWaveformPattern = randomPattern;
console.log('Progress Waveform: Using realistic pattern -', randomPattern);
// Generate the waveform with more samples for detailed appearance (increased to 400 for longer/denser waveform)
progressWaveformData = waveformPatterns[randomPattern](400);
drawProgressWaveform();
// Real-time audio capture disabled to maintain consistent waveform
// Using realistic fallback patterns only for stable, predictable appearance
console.log('Progress Waveform: Using static realistic pattern (real-time capture disabled)');
}
// Draw progress waveform with progress coloring
function drawProgressWaveform() {
if (!progressWaveformCtx || !progressWaveformCanvas) return;
var width = progressWaveformCanvas.width;
var height = progressWaveformCanvas.height;
// Clear canvas
progressWaveformCtx.clearRect(0, 0, width, height);
// If no waveform data, just return (canvas will be empty)
if (progressWaveformData.length === 0) return;
// Get playback progress
var currentTime = miniPlayer.data('jPlayer') ? miniPlayer.data('jPlayer').status.currentTime : 0;
var duration = miniPlayer.data('jPlayer') ? miniPlayer.data('jPlayer').status.duration : 0;
var progress = duration > 0 ? (currentTime / duration) : 0;
var progressX = width * progress;
// Draw waveform bars - optimized for 400 samples (denser, longer appearance)
var barCount = progressWaveformData.length;
var barWidth = (width / barCount) * 0.82;
var gap = (width / barCount) * 0.18;
for (var i = 0; i < barCount; i++) {
var x = i * (barWidth + gap);
// Enhanced bar height calculation with micro-variations for realism
var baseHeight = progressWaveformData[i] * height * 0.85;
// Add subtle random variation to mimic analog waveform (�2%)
var microVariation = (Math.random() - 0.5) * 0.04;
var barHeight = baseHeight * (1 + microVariation);
var y = (height - barHeight) / 2;
// Determine color based on progress
if (x < progressX) {
// Played section - light blue with slight alpha variation for depth
var alpha = 0.95 + (Math.random() * 0.05);
progressWaveformCtx.fillStyle = 'rgba(135, 206, 250, ' + alpha + ')'; // Light blue (skyblue)
} else {
// Unplayed section - dimmed gray with slight variation
var alpha = 0.28 + (Math.random() * 0.04);
progressWaveformCtx.fillStyle = 'rgba(255, 255, 255, ' + alpha + ')';
}
// Draw bar with rounded tops for smoother appearance
progressWaveformCtx.beginPath();
var radius = Math.min(barWidth / 2, 1);
progressWaveformCtx.moveTo(x, y + radius);
progressWaveformCtx.arcTo(x, y, x + barWidth, y, radius);
progressWaveformCtx.lineTo(x + barWidth, y + barHeight - radius);
progressWaveformCtx.arcTo(x + barWidth, y + barHeight, x, y + barHeight, radius);
progressWaveformCtx.lineTo(x, y + radius);
progressWaveformCtx.fill();
}
// Draw hover preview line
if (progressHoverX >= 0) {
progressWaveformCtx.strokeStyle = 'rgba(255, 255, 255, 0.6)';
progressWaveformCtx.lineWidth = 2;
progressWaveformCtx.setLineDash([4, 4]);
progressWaveformCtx.beginPath();
progressWaveformCtx.moveTo(progressHoverX, 0);
progressWaveformCtx.lineTo(progressHoverX, height);
progressWaveformCtx.stroke();
progressWaveformCtx.setLineDash([]);
}
}
// Initialize jPlayer instance
var miniPlayer = $('#mini_player_jp');
miniPlayer.jPlayer({
swfPath: "http://mediaistream.com/modules/jrCore/contrib/jplayer",
ready: function() {
console.log('Mini Player: jPlayer ready');
// Initialize waveform once - MUST be done only once per audio element
var audioElement = $('#mini_player_jp audio')[0];
if (audioElement && !audioContext) {
console.log('Mini Player: Initializing waveform');
initWaveform(audioElement);
}
return true;
},
cssSelectorAncestor: "#mini_player_container",
supplied: 'mp3,oga',
solution: "html,flash",
volume: 0.7,
wmode: 'window',
consoleAlerts: true,
preload: 'none',
play: function() {
miniPlayer.jPlayer("pauseOthers");
// Resume audio context if it's suspended
if (audioContext) {
if (audioContext.state === 'suspended') {
audioContext.resume().then(function() {
console.log('Waveform: AudioContext resumed');
});
}
// Resume waveform animation
if (analyser && !animationId) {
drawWaveform();
}
}
},
pause: function() {
// Clear auto-resume when user manually pauses
console.log('Mini Player: Paused by user');
// Stop waveform animation
stopWaveform();
try {
var savedState = localStorage.getItem('miniPlayerState');
if (savedState) {
var state = JSON.parse(savedState);
state.isPlaying = false;
localStorage.setItem('miniPlayerState', JSON.stringify(state));
}
} catch(e) {
console.warn('Could not update pause state:', e);
}
},
ended: function() {
console.log('Mini Player: Track ended');
stopWaveform();
// Play next track from queue
if (window.QueueManager) {
var nextTrack = window.QueueManager.getNextTrack();
if (nextTrack && window.MiniPlayerJPlayer) {
window.MiniPlayerJPlayer.loadAndPlay(nextTrack);
}
}
},
timeupdate: function(event) {
// Update progress bar and time display
if (event.jPlayer.status.duration) {
// Calculate progress
var progress = event.jPlayer.status.currentTime / event.jPlayer.status.duration;
// Update progress ring
updateProgressRing(progress);
// Update progress waveform
drawProgressWaveform();
// Use jPlayer's built-in time conversion
var currentTimeFormatted = $.jPlayer.convertTime(event.jPlayer.status.currentTime);
var durationFormatted = $.jPlayer.convertTime(event.jPlayer.status.duration);
$('#mini_player_current_time').text(currentTimeFormatted);
$('#mini_player_duration').text(durationFormatted);
// Save playback state for continuous play across pages
if (window.MiniPlayerJPlayer && window.MiniPlayerJPlayer.currentTrack) {
var playbackState = {
track: window.MiniPlayerJPlayer.currentTrack,
position: event.jPlayer.status.currentTime,
isPlaying: !event.jPlayer.status.paused,
timestamp: Date.now()
};
try {
localStorage.setItem('miniPlayerState', JSON.stringify(playbackState));
} catch(e) {
console.warn('Could not save playback state:', e);
}
}
}
},
error: function(event) {
console.error('jPlayer Error:', event.jPlayer.error);
console.error('jPlayer Error Type:', event.jPlayer.error.type);
console.error('jPlayer Error Context:', event.jPlayer.error.context);
console.error('jPlayer Error Message:', event.jPlayer.error.message);
}
});
// Play button click handler - like jrAudio_button
var playBtn = $('#mini_player_play_btn');
playBtn.click(function(e) {
console.log('Mini Player: Play button clicked - will be set by loadAndPlay');
e.preventDefault();
});
// Mini Player Controller - jPlayer based
window.MiniPlayerJPlayer = {
currentTrack: null,
loadAndPlay: function(track) {
console.log('=== jPlayer Mini Player: loadAndPlay ===');
console.log('Full track object:', track);
console.log('Track ID:', track.id);
console.log('Track title:', track.title);
console.log('Track artist:', track.artist);
console.log('Track artwork:', track.artwork);
console.log('Track audioUrl:', track.audioUrl);
// Check if track.id exists, if not try track._item_id
var trackId = track.id || track._item_id || track.item_id;
console.log('Resolved track ID:', trackId);
if (!track || !trackId) {
console.error('jPlayer Mini Player: Invalid track - missing ID');
console.error('Track object keys:', Object.keys(track));
return;
}
this.currentTrack = track;
// Add track change animation
var $miniPlayer = $('#mini-player');
$miniPlayer.addClass('loading');
// Fade out info, update, fade in
var $title = $('#mini_player_title');
var $artist = $('#mini_player_artist');
$title.css('opacity', '0').text(track.title || 'Unknown Track');
$artist.css('opacity', '0').text(track.artist || 'Unknown Artist');
setTimeout(function() {
$title.css({'opacity': '1', 'transition': 'opacity 0.4s ease'});
$artist.css({'opacity': '1', 'transition': 'opacity 0.4s ease'});
$miniPlayer.removeClass('loading');
}, 100);
console.log('jPlayer Mini Player: Title and Artist updated with animation');
// Update UI - Artwork - use the URL from data-track-artwork
var artworkContainer = $('#mini_player_artwork_container');
console.log('jPlayer Mini Player: Track artwork URL:', track.artwork);
// Reset progress ring
updateProgressRing(0);
if (track.artwork && track.artwork.length > 10) {
// Use the artwork URL directly from the button data attribute
// Format: http://mediaistream.com/audio/image/audio_image//small
console.log('jPlayer Mini Player: Setting artwork to:', track.artwork);
var imgHtml = '
';
artworkContainer.html(imgHtml);
// Extract and apply color theme from artwork
var img = artworkContainer.find('img')[0];
if (img) {
if (img.complete) {
var color = extractColorFromImage(img);
applyThemeColor(color);
} else {
img.onload = function() {
var color = extractColorFromImage(img);
applyThemeColor(color);
};
}
}
} else if (trackId) {
// Fallback: build image URL using Jamroom standard format
// This matches jrAudio_black_overlay_player.tpl format
var imgUrl = jamroomUrl + '/' + jrAudioModuleUrl + '/image/audio_image/' + trackId + '/small';
console.log('jPlayer Mini Player: Using fallback artwork URL:', imgUrl);
var imgHtml = '
';
artworkContainer.html(imgHtml);
// Extract and apply color theme from artwork
var img = artworkContainer.find('img')[0];
if (img) {
if (img.complete) {
var color = extractColorFromImage(img);
applyThemeColor(color);
} else {
img.onload = function() {
var color = extractColorFromImage(img);
applyThemeColor(color);
};
}
}
} else {
console.warn('jPlayer Mini Player: No artwork available - using default');
artworkContainer.html('
');
// Reset to default theme
applyThemeColor('#1db954');
}
// Use the audioUrl from track data if available, otherwise construct it
// The audioUrl comes from JavaScript-constructed URL with proper module path
var streamUrl;
if (track.audioUrl && track.audioUrl.length > 10) {
streamUrl = track.audioUrl;
console.log('jPlayer Mini Player: Using provided audioUrl:', streamUrl);
} else {
// Fallback: construct URL using module URL and track ID
streamUrl = jamroomUrl + '/' + jrAudioModuleUrl + '/stream/audio_file/' + trackId + '/key=1/file.mp3';
console.log('jPlayer Mini Player: Constructed stream URL from ID:', trackId);
console.log('jPlayer Mini Player: Stream URL:', streamUrl);
}
// Load media and play - like jrAudio_button.tpl
miniPlayer.jPlayer("clearMedia");
miniPlayer.jPlayer("setMedia", {
mp3: streamUrl
});
miniPlayer.jPlayer("play")
// Generate progress waveform (will start after audio begins playing)
setTimeout(function() {
generateProgressWaveform();
}, 500);
// Show mini player
$('#mini-player').addClass('active').css('transform', 'translateY(0)');
console.log('jPlayer Mini Player: Playing');
}
};
// Handle window resize for waveform canvases
$(window).on('resize', function() {
if (waveformCanvas) {
waveformCanvas.width = waveformCanvas.offsetWidth;
waveformCanvas.height = waveformCanvas.offsetHeight;
}
if (progressWaveformCanvas) {
progressWaveformCanvas.width = progressWaveformCanvas.offsetWidth;
progressWaveformCanvas.height = progressWaveformCanvas.offsetHeight;
drawProgressWaveform();
}
});
// Make progress waveform clickable for seeking
$(progressWaveformCanvas).on('click', function(e) {
if (!miniPlayer.data('jPlayer')) return;
var rect = progressWaveformCanvas.getBoundingClientRect();
var x = e.clientX - rect.left;
var percent = x / rect.width;
var duration = miniPlayer.data('jPlayer').status.duration;
if (duration > 0) {
var seekTime = duration * percent;
miniPlayer.jPlayer('play', seekTime);
console.log('Progress Waveform: Seeking to', seekTime.toFixed(2), 'seconds');
}
});
// Track mouse position for hover preview on progress waveform
$(progressWaveformCanvas).on('mousemove', function(e) {
var rect = progressWaveformCanvas.getBoundingClientRect();
progressHoverX = e.clientX - rect.left;
drawProgressWaveform();
});
// Clear hover preview when mouse leaves progress waveform
$(progressWaveformCanvas).on('mouseleave', function() {
progressHoverX = -1;
drawProgressWaveform();
});
// Add hover effect to progress waveform
$(progressWaveformCanvas).css({
'cursor': 'pointer'
});
// Initialize progress waveform canvas size
if (progressWaveformCanvas) {
progressWaveformCanvas.width = progressWaveformCanvas.offsetWidth;
progressWaveformCanvas.height = progressWaveformCanvas.offsetHeight;
console.log('Progress Waveform: Canvas initialized');
}
console.log('MINI PLAYER: jPlayer initialized and ready');
// Save current playback state before page unload
$(window).on('beforeunload', function() {
try {
var status = miniPlayer.data('jPlayer').status;
if (window.MiniPlayerJPlayer && window.MiniPlayerJPlayer.currentTrack && !status.paused) {
var playbackState = {
track: window.MiniPlayerJPlayer.currentTrack,
position: status.currentTime || 0,
isPlaying: true,
timestamp: Date.now()
};
localStorage.setItem('miniPlayerState', JSON.stringify(playbackState));
console.log('MINI PLAYER: Saved state before unload');
}
} catch(e) {
console.warn('Could not save state on unload:', e);
}
});
// Restore playback state for continuous play across pages
setTimeout(function() {
try {
var savedState = localStorage.getItem('miniPlayerState');
console.log('MINI PLAYER: Checking for saved state...', savedState ? 'Found' : 'Not found');
if (savedState) {
var state = JSON.parse(savedState);
var timeSinceLastPlay = Date.now() - (state.timestamp || 0);
console.log('MINI PLAYER: State details:', state);
console.log('MINI PLAYER: Time since last play:', timeSinceLastPlay, 'ms');
console.log('MINI PLAYER: Is playing:', state.isPlaying);
// Only restore if it was playing recently (within 5 minutes)
if (timeSinceLastPlay < 300000 && state.track && state.isPlaying) {
console.log('MINI PLAYER: Restoring playback...');
console.log('MINI PLAYER: Track:', state.track.title);
console.log('MINI PLAYER: Position:', state.position);
// Store track in MiniPlayerJPlayer
if (window.MiniPlayerJPlayer) {
window.MiniPlayerJPlayer.currentTrack = state.track;
}
// Update UI immediately
$('#mini_player_title').text(state.track.title || 'Unknown Track');
$('#mini_player_artist').text(state.track.artist || 'Unknown Artist');
if (state.track.artwork) {
var imgHtml = '
';
$('#mini_player_artwork_container').html(imgHtml);
}
// Show mini player
$('#mini-player').addClass('active').css('transform', 'translateY(0)');
// Load and play from saved position
if (state.track.audioUrl) {
console.log('MINI PLAYER: Loading audio:', state.track.audioUrl);
miniPlayer.jPlayer("setMedia", {
mp3: state.track.audioUrl
});
// Play from saved position
setTimeout(function() {
if (state.position && state.position > 0) {
console.log('MINI PLAYER: Playing from position:', state.position);
miniPlayer.jPlayer("play", state.position);
} else {
console.log('MINI PLAYER: Playing from start');
miniPlayer.jPlayer("play");
}
}, 100);
}
} else {
console.log('MINI PLAYER: Not restoring - conditions not met');
if (timeSinceLastPlay >= 300000) console.log('MINI PLAYER: Too old');
if (!state.isPlaying) console.log('MINI PLAYER: Was paused');
}
}
} catch(e) {
console.error('MINI PLAYER: Error restoring playback:', e);
}
}, 1000);
});